package com.wan.net.core.client;

import cn.hutool.core.net.Ipv4Util;
import com.wan.net.config.HostAndPort;
import com.wan.net.core.packet.Packet;
import com.wan.net.core.udp.UdpCodec;
import com.wan.net.core.udp.UdpPacket;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;

import javax.annotation.Nullable;

/**
 * 支持广播和单播
 * 端口绑定0
 * 广播：包体使用UdpPacket，host填255.255.255.255即可对该消息进行全网段广播
 * 传入指定地址和端口只对该地址传输，广播失效
 * @author wwh
 * @date 2022/6/16 15:14
 */
public class UdpClient extends AbstractClient{
    public UdpClient(HostAndPort host) {
        super(host);
    }

    public UdpClient(HostAndPort hostAndPort, @Nullable ChannelHandler channelHandler) {
        super(hostAndPort, channelHandler);
    }

    public UdpClient(HostAndPort hostAndPort, int idleTimeout) {
        super(hostAndPort, idleTimeout);
    }

    public UdpClient(HostAndPort hostAndPort, int idleTimeout, @Nullable ChannelHandler channelHandler) {
        super(hostAndPort, idleTimeout, channelHandler);
    }

    @Override
    public synchronized void open() {
        this.bootstrap = new Bootstrap();
        this.bootstrap.group(nioEventLoopGroup)
                .channel(Epoll.isAvailable() ? EpollDatagramChannel.class : NioDatagramChannel.class)
                .option(ChannelOption.SO_BROADCAST, true)
                .handler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
                        ChannelPipeline pipeline = channel.pipeline();
                        //pipeline.addLast(new IdleStateHandler(0,0,idleTimeout));
                        channelChannelInitializer(pipeline);
                        pipeline.addLast(channelHandler);
                    }
                });
        ChannelFuture channelFuture;
        if(hostAndPort.getPort() == 0){
            //udp可以不指定目标地址，可以广播,根据包体地址广播
            channelFuture = bootstrap.bind(0);
        }else{
            channelFuture = bootstrap.connect(hostAndPort.getHost(),hostAndPort.getPort());
        }
        channelFuture.syncUninterruptibly();

        if (channelFuture.isSuccess()) {
            if (channelFuture.channel().isActive()) {
                channel = channelFuture.channel();
                ClientFactory.add(this);
                logger.info("{} client started at [{}]",getClientType().name().toLowerCase(), channel.localAddress());
            }
        } else if (channelFuture.cause() != null) {
            logger.error("启动客户端失败",channelFuture.cause());
        } else {
            logger.error("启动客户端[client:{}]未知错误", this);
        }
    }

    @Override
    public void send(Packet packet) {
        boolean bind = hostAndPort.getPort() != 0;
        if(bind) {
            UdpPacket udpPacket = new UdpPacket();
            udpPacket.setHost(hostAndPort.getHost());
            udpPacket.setPort(hostAndPort.getPort());
            udpPacket.setMessageId(packet.getMessageId());
            udpPacket.setContent(packet.getContent());
            super.send(udpPacket);
            return;
        }
        boolean udp = packet instanceof UdpPacket;
        if(!udp){
            logger.error("udp发送消息未指定目标地址！请使用UdpPacket,msgId:{}",packet.getMessageId());
        }
        super.send(packet);
    }

    @Override
    protected void channelChannelInitializer(ChannelPipeline pipeline) {
        pipeline.addLast(new UdpCodec());
    }

    @Override
    public ClientType getClientType() {
        return ClientType.UDP;
    }

    @Override
    protected void onInactive() {
        open();
    }
}
